﻿using System;
using System.Windows;

//
// Interpolators originally adapted by Kenny Young from "Custom Animation Sample" at http://msdn2.microsoft.com/en-us/library/ms771715.aspx
//
namespace Canyala.Lagoon.Silverlight
{
	public abstract class Interpolation
	{
		public abstract double GetAlpha(double progress);
	}

	public class LinearInterpolation : Interpolation
	{
		public override double GetAlpha(double progress)
		{
			return progress;
		}
	}

	public abstract class EasedInterpolation : Interpolation
	{
        public EasedInterpolation()
        {
            EdgeBehavior = EdgeBehaviorEnum.EaseOut;
        }

        public EasedInterpolation(EdgeBehaviorEnum edgeBehavior)
        {
            EdgeBehavior = edgeBehavior;
        }

		public enum EdgeBehaviorEnum { EaseIn, EaseOut, EaseInOut }

		public EdgeBehaviorEnum EdgeBehavior { get; set; }

		protected abstract double GetEaseInAlpha(double progress);
		protected abstract double GetEaseOutAlpha(double progress);

		protected virtual double GetEaseInOutAlpha(double timeFraction)
		{
			double returnValue = 0.0;

			// we cut each effect in half by multiplying the time fraction by two and halving the distance.
			if (timeFraction <= 0.5)
			{
				returnValue = 0.5 * this.GetEaseInAlpha(timeFraction * 2);
			}
			else
			{
				returnValue = 0.5 + 0.5 * this.GetEaseOutAlpha((timeFraction - 0.5) * 2);
			}
			return returnValue;
		}

		public override double GetAlpha(double progress)
		{
			switch (this.EdgeBehavior)
			{
				case EdgeBehaviorEnum.EaseIn:
					return this.GetEaseInAlpha(progress);
				case EdgeBehaviorEnum.EaseOut:
					return this.GetEaseOutAlpha(progress);
				case EdgeBehaviorEnum.EaseInOut:
				default:
					return this.GetEaseInOutAlpha(progress);
			}
		}
	}

	public class BounceInterpolation : EasedInterpolation
	{
        public BounceInterpolation() : this(2, 3, EdgeBehaviorEnum.EaseOut) { }

        public BounceInterpolation(int bounces, double bounciness, EdgeBehaviorEnum edgeBehavior)
            : base(edgeBehavior)
        {
            Bounces = bounces;
            Bounciness = bounciness;
        }

		public int Bounces { get; set; }

		public double Bounciness { get; set; }

		protected override double GetEaseOutAlpha(double timeFraction)
		{
			double returnValue = 0.0;

			// math magic: The cosine gives us the right wave, the timeFraction is the frequency of the wave, 
			// the absolute value keeps every value positive (so it "bounces" off the midpoint of the cosine 
			// wave, and the amplitude (the exponent) makes the sine wave get smaller and smaller at the end.
			returnValue = Math.Abs(Math.Pow((1 - timeFraction), this.Bounciness)
						  * Math.Cos(2 * Math.PI * timeFraction * this.Bounces));
			returnValue = 1 - returnValue;
			return returnValue;
		}

		protected override double GetEaseInAlpha(double timeFraction)
		{
			double returnValue = 0.0;
			// math magic: The cosine gives us the right wave, the timeFraction is the amplitude of the wave, 
			// the absolute value keeps every value positive (so it "bounces" off the midpoint of the cosine 
			// wave, and the amplitude (the exponent) makes the sine wave get bigger and bigger towards the end.
			returnValue = Math.Abs(Math.Pow((timeFraction), this.Bounciness)
						  * Math.Cos(2 * Math.PI * timeFraction * this.Bounces));
			return returnValue;
		}
	}

	public class ElasticInterpolation : EasedInterpolation
	{
        public ElasticInterpolation() : this(4, 3, EdgeBehaviorEnum.EaseOut) { }

        public ElasticInterpolation(double springiness, double oscillations, EdgeBehaviorEnum edgeBehavior)
            : base(edgeBehavior)
        {
            Springiness = springiness;
            Oscillations = oscillations;
        }

        public double Springiness { get; set; }

		public double Oscillations { get; set; }

		protected override double GetEaseOutAlpha(double timeFraction)
		{
			double returnValue = 0.0;

			// math magic: The cosine gives us the right wave, the timeFraction * the # of oscillations is the 
			// frequency of the wave, and the amplitude (the exponent) makes the wave get smaller at the end
			// by the "springiness" factor. This is extremely similar to the bounce equation.
			returnValue = Math.Pow((1 - timeFraction), this.Springiness)
						  * Math.Cos(2 * Math.PI * timeFraction * this.Oscillations);
			returnValue = 1 - returnValue;
			return returnValue;
		}

		protected override double GetEaseInAlpha(double timeFraction)
		{
			double returnValue = 0.0;
			// math magic: The cosine gives us the right wave, the timeFraction * the # of oscillations is the 
			// frequency of the wave, and the amplitude (the exponent) makes the wave get smaller at the beginning
			// by the "springiness" factor. This is extremely similar to the bounce equation. 
			returnValue = Math.Pow((timeFraction), this.Springiness)
						  * Math.Cos(2 * Math.PI * timeFraction * this.Oscillations);
			return returnValue;
		}
	}

	public class BackInterpolation : EasedInterpolation
	{
        public BackInterpolation() : this(.5, 2, EdgeBehaviorEnum.EaseOut) { }

        public BackInterpolation(double amplitude, double suppression, EdgeBehaviorEnum edgeBehavior)
            : base(edgeBehavior)
        {
            Amplitude = amplitude;
            Suppression = suppression;
        }

		public double Amplitude { get; set; }

		public double Suppression { get; set; }

		protected override double GetEaseOutAlpha(double timeFraction)
		{
			double returnValue = 0.0;
			double frequency = 0.5;

			// math magic: The sine gives us the right wave, the timeFraction * 0.5 (frequency) gives us only half 
			// of the full wave, the amplitude gives us the relative height of the peak, and the exponent makes the 
			// effect drop off more quickly by the "suppression" factor. 
			returnValue = Math.Pow((timeFraction), this.Suppression)
						  * this.Amplitude * Math.Sin(2 * Math.PI * timeFraction * frequency) + timeFraction;
			return returnValue;
		}

		protected override double GetEaseInAlpha(double timeFraction)
		{
			double returnValue = 0.0;
			double frequency = 0.5;

			// math magic: The sine gives us the right wave, the timeFraction * 0.5 (frequency) gives us only half 
			// of the full wave (flipped by multiplying by -1 so that we go "backwards" first), the amplitude gives 
			// us the relative height of the peak, and the exponent makes the effect start later by the "suppression" 
			// factor. 
			returnValue = Math.Pow((1 - timeFraction), this.Suppression)
						  * this.Amplitude * Math.Sin(2 * Math.PI * timeFraction * frequency) * -1 + timeFraction;
			return returnValue;
		}
	}

	public class ExponentialInterpolation : EasedInterpolation
	{
        public ExponentialInterpolation() : this(2, EdgeBehaviorEnum.EaseInOut) { }

        public ExponentialInterpolation(double power, EdgeBehaviorEnum edgeBehavior)
            : base(edgeBehavior)
        {
            Power = power;
        }

		public double Power { get; set; }

		protected override double GetEaseInAlpha(double timeFraction)
		{
			double returnValue = 0.0;

			// math magic: simple exponential growth
			returnValue = Math.Pow(timeFraction, this.Power);
			return returnValue;
		}

		protected override double GetEaseOutAlpha(double timeFraction)
		{
			double returnValue = 0.0;

			// math magic: simple exponential decay
			returnValue = Math.Pow(timeFraction, 1 / this.Power);
			return returnValue;
		}
	}
}
